#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "memorystream.h"

void memorystream_create (MemoryStream *ms, int init_size)
{
  Stream *s = STREAM_CAST (ms);
  stream_create (s);

  s->write = memorystream_write;
  s->read = memorystream_read;
  s->seek = memorystream_seek;
  s->pos = memorystream_pos;
  s->size = memorystream_size;
  s->close = memorystream_close;

  ms->buff_size = init_size;
  ms->buff_off = 0;
  ms->nbytes = 0;
  ms->buff = malloc (init_size);
  assert (ms->buff);
}

void * memorystream_pointer_from_offset (MemoryStream *ms, int offset)
{
  if (offset >= ms->stream.size (STREAM_CAST (ms)))
    return NULL;

  return ms->buff + offset;
}

void memorystream_write (Stream *s, void *src, int size)
{
  MemoryStream *ms = MEMORYSTREAM_CAST (s);

  while (ms->buff_off + size > ms->buff_size)
    memorystream_expand_buffer (ms);

  memcpy (ms->buff + ms->buff_off, src, size);
  ms->buff_off += size;

  if (ms->buff_off > ms->nbytes)
    ms->nbytes = ms->buff_off;
}

int memorystream_read (Stream *s, void *dest, int num_read, int dest_offset)
{
  MemoryStream *ms = MEMORYSTREAM_CAST (s);
  int nread;

  if (ms->buff_off + num_read > ms->nbytes)
    nread = ms->nbytes - ms->buff_off;
  else
    nread = num_read;

  memcpy ((char *)dest + dest_offset, ms->buff + ms->buff_off, nread);

  ms->buff_off += nread;

  return nread;
}

void memorystream_seek (Stream *s, int offset, SeekWhence whence)
{
  MemoryStream *ms = MEMORYSTREAM_CAST (s);
  int npos;

  switch (whence) {
    case SeekSet:
      npos = offset;
      break;

    case SeekCur:
      npos = ms->buff_off + offset;
      break;

    case SeekEnd:
      npos = ms->nbytes + offset;
      break;
  }

  if (npos > ms->nbytes) {
    if (npos > ms->buff_size)
      memorystream_expand_buffer (ms);

    ms->nbytes = npos;
  }

  ms->buff_off = npos;
}

int memorystream_pos (Stream *s)
{
  MemoryStream *ms = MEMORYSTREAM_CAST (s);

  return ms->buff_off;
}

int memorystream_size (Stream *s)
{
  MemoryStream *ms = MEMORYSTREAM_CAST (s);

  return ms->nbytes;
}

void memorystream_close (Stream *s)
{
  MemoryStream *ms = MEMORYSTREAM_CAST (s);

  free (ms->buff);
}

void memorystream_expand_buffer (MemoryStream *ms)
{
  ms->buff = realloc (ms->buff, ms->buff_size * 2);
  assert (ms->buff);
  ms->buff_size *= 2;
}
